import pandas as pd
import numpy as np
from utilities.utilities_func import set_limit_type
from .dataLoader import DataLoader
from .brokers import Broker
from .portfolio import PortTrack
from .paramsPipe import ParamsPipe


class Order:
    """
    提供2种下单形式，单笔交易以及批量下单
    下单形式：按手数（size）下单，或者按目标权重下单

    """

    def __init__(self, data: DataLoader, broker: Broker, port: PortTrack, params: ParamsPipe):
        self.cash = 0  # 当前现金头寸
        self.port_value = 0  # 当前组合价值
        self.code = None
        self.order_status = None  # 订单状态：0-未执行, 1-订单完成, 2-下单失败，超过成交量, 3-下单失败：无足够现金, 4-下单失败：已触达停板规则,  5-订单创建失败
        self.broker = broker
        self.data = data  # 传入数据管线
        self.port = port  # 调用组合类
        self.params = params  # 参数传递管线

    def buy(self, code, next_row, limit_type, price=None, size=None):
        #  单笔订单买入 如果未指定价格，则价格为默认下一交易日开盘价
        if price is None or np.isnan(price):
            if next_row > len(self.data.unique_dates):
                self.order_status = 5
                print("---------- 默认执行价格订单创建失败：无下一交易日数据")
                return
            else:
                price = self.data.get_open(self.params.next_day, code)  # 获取开盘价
                if price is None:
                    print(f"买入下单失败，获取开盘价数据错误，股票代码:{code}, 交易日:{self.params.next_day}")
                    self.order_status = 5
                    self.params.add_stocks_unsettled(stock=code, size=size, price=price, direction=1)  # 记录无价格导致失败订单
                    return

        status, order_result = self.broker.execute(code, 1, size, price, limit_type)
        self.order_status = status
        if self.order_status != 1:
            self.params.add_stocks_unsettled(stock=code, size=size, price=price, direction=1)  # 记录交易执行时失败订单
        self.notify_order(code, self.params.next_day, '买入')
        return

    def sell(self, code, next_row, limit_type, price=None, size=None):
        # 单笔卖出 如果未指定价格，则价格为默认下一交易日开盘价

        if price is None or np.isnan(price):
            if next_row > len(self.data.unique_dates):
                self.order_status = 5
                print("---------- 默认执行价格订单创建失败：无下一交易日数据")
                return
            else:
                price = self.data.get_open(self.params.next_day, code)
                if price is None:
                    print(f"卖出失败，获取开盘价数据错误，股票代码:{code}, 交易日:{self.params.next_day}")
                    self.order_status = 5
                    self.params.add_stocks_unsettled(stock=code, size=size, price=price, direction=-1)
                    return

        # 卖出订单需要指定卖出数量
        if size is None:
            if code not in self.port.holdings_dict.keys():
                self.order_status = 5
                print("---------- 卖出订单创建失败：无持仓")
                return
            else:
                size = self.port.holdings_dict[code]['holding_num']  # 默认全部清仓

        status, order_result = self.broker.execute(code, -1, size, price, limit_type)
        self.order_status = status
        if self.order_status != 1:
            self.params.add_stocks_unsettled(stock=code, size=size, price=price, direction=-1)  # 记录交易执行时失败订单
        self.notify_order(code, self.params.next_day, '买入')
        return

    def _sell_all(self, order_data: pd.DataFrame, next_row):
        # 将持仓股票中不属于目标持仓的股票，全部清仓
        _sell_all = set(self.port.holdings['code'].unique().tolist()) - set(order_data['code'].tolist())
        if len(_sell_all) > 0:
            for i in _sell_all:
                _limit_type = set_limit_type(i)
                if i in self.port.holdings_dict.keys():
                    _size = self.port.holdings_dict[i]['holding_num']
                else:
                    try:
                        # 持仓字典中无法查询到再去持仓原始数据中查询
                        _size = self.port.holdings.loc[self.port.holdings['code'] == i, 'holding_num'].values[0]
                    except:
                        print(f"股票{i} 持仓数据获取失败，日期为{self.params.today}")
                        continue
                self.sell(code=i, next_row=next_row, limit_type=_limit_type, size=_size)  # 所有持仓数量全部卖出

    def bulk_order_size(self, order_data: pd.DataFrame, next_row):
        """
            批量下单至指定数量
            :param order_data: dataframe, columns=['code', 'size', 'direction', 'price'，‘limit_type’]
                * direction: 0-买入，1-卖出
            :param next_row: 下一交易日索引
        """
        _sell = order_data.loc[order_data['direction'] == 1, :]
        _sell_list = set(_sell['code'].tolist()) - set(self.port.holdings['code'].tolist())
        _buy = order_data.loc[order_data['direction'] == 0, :]
        _buy_list = set(_buy['code'].tolist()) - set(self.port.holdings['code'].tolist())

        # 清空需要被调仓股票
        self._sell_all(order_data, next_row)

        if _sell.empty:
            print("---------- 无卖出订单")
        else:
            _sell.reset_index(drop=True, inplace=True)
            for i in _sell_list:
                _index = _sell.loc[_sell['code'] == i].index[0]
                _size = None if np.isnan(_sell.loc[_index, 'size'].values[0]) else _sell.loc[_index, 'size'].values[0]
                _price = None if np.isnan(_sell.loc[_index, 'price'].values[0]) else _sell.loc[_index, 'price'].values[
                    0]
                _limit_type = _sell.loc[_index, 'limit_type']

                self.sell(code=i, next_row=next_row, limit_type=_limit_type, price=_price, size=_size)

        if _buy.empty:
            print("---------- 无买入订单")
        else:
            _buy.reset_index(drop=True, inplace=True)
            for i in _buy_list:
                _index = _buy.loc[_buy['code'] == i].index[0]
                _size = None if np.isnan(_buy.loc[_index, 'size']) else _buy.loc[_index, 'size']
                _price = None if np.isnan(_buy.loc[_index, 'price']) else _buy.loc[_index, 'price']
                _limit_type = _buy.loc[i, 'limit_type']

                self.buy(code=i, next_row=next_row, limit_type=_limit_type, price=_price, size=_size)

    def bulk_order_targetWeight(self, order_data: pd.DataFrame, next_row):
        """
            批量下单至指定权重
            :param order_data: dataframe, columns=['code','target_weight', ‘limit_type’]
                * direction: 0-买入，1-卖出
            :param next_row: 下一交易日索引
        """
        if order_data.empty:
            print("---------- 无订单")
        else:
            # 已持仓不属于新的信号范围内股票清仓
            self._sell_all(order_data, next_row)

            excute_orders = []
            # 优先处理高信号值股票
            for i in range(len(order_data)):
                _code = order_data.loc[i, 'code']
                _target_weight = order_data.loc[i, 'target_weight']
                _limit_type = order_data.loc[i, 'limit_type']
                # 默认以当前收盘价计算标的在组合中的权重，再平衡以次日收盘价下单
                _price = self.data.get_open(self.params.today, _code)
                if _price is None:
                    print(f"---------- 获取标的{_code}开盘价失败, 无法下单")
                    continue
                if _code in self.port.holdings_dict.keys():
                    _size = self.port.holdings_dict[_code]['holding_num']  # 当前持仓数量
                    _current_weight = _size * _price / self.port.port_value - _target_weight  # 目标权重与当前交易日收盘价权重差值
                    if _current_weight == 0:
                        print(f"---------- 资产{_code}，目标权重与当前权重一致，无需下单")
                    elif _current_weight > 0:
                        # 目标权重小于当前权重，卖出...卖出手数按当前收盘价计算
                        _size = _current_weight * self.port.port_value / _price
                        excute_orders.append((_code, _size, _limit_type, -1))
                    else:
                        # 目标权重大于当前权重，买入...按当前收盘价计算买入手数
                        _size = abs(_current_weight) * self.port.port_value / _price
                        excute_orders.append((_code, _size, _limit_type, 1))
                else:
                    # 无持仓股票
                    if _target_weight <= 0:
                        print(f"---------- 资产{_code}，目标权重小于等于0，无法下单")
                    else:
                        # 按当前收盘价计算需要下单的手数
                        _size = _target_weight * self.port.port_value / _price
                        excute_orders.append((_code, _size, _limit_type, 1))

            excute_order = pd.DataFrame(data={
                'code': [j[0] for j in excute_orders],
                'size': [j[1] for j in excute_orders],
                'limit_type': [j[2] for j in excute_orders],
                'direction': [j[3] for j in excute_orders],
            })
            excute_order.sort_values(by=['direction', 'size'], ascending=[True, False], inplace=True)

            for l in range(len(excute_order)):
                _code = excute_order.iloc[l, 0]
                _size = excute_order.iloc[l, 1]
                _limit_type = excute_order.iloc[l, 2]
                _direction = excute_order.iloc[l, 3]

                if _direction == -1:
                    self.sell(_code, next_row, _limit_type, size=_size)
                else:
                    self.buy(_code, next_row, _limit_type, size=_size)

    def to_targetWeight(self, code, next_row, limit_type, target_weight, port_value, holdings: pd.DataFrame):
        """
        针对单一资产，按目标权重下单
        :param
            code: 资产编码, str
            target_weight: 目标权重， float
            holdings: 持仓记录，port类的holdings属性
        """
        # 默认以当前收盘价计算标的在组合中的权重，再平衡以次日收盘价下单
        _price = self.data.get_close(self.params.today, code)
        if _price is None:
            print(f"---------- 获取标的{code}收盘价失败, 无法下单")
            return

        if code in self.port.holdings_dict.keys():
            _size = self.port.holdings_dict[code]['holding_num']  # 当前持仓数量
            _current_weight = _size * _price / port_value - target_weight  # 目标权重与当前交易日收盘价权重差值

            if _current_weight == 0:
                print(f"---------- 资产{code}，目标权重与当前权重一致，无需下单")
            elif _current_weight > 0:
                # 目标权重小于当前权重，卖出...卖出手数按当前收盘价计算
                _size = _current_weight * port_value / _price
                self.sell(code, next_row, limit_type, size=_size)
            else:
                # 目标权重大于当前权重，买入...按当前收盘价计算买入手数
                _size = abs(_current_weight) * port_value / _price
                self.buy(code, next_row, limit_type, size=_size)
        else:
            # 无持仓股票
            if target_weight <= 0:
                print(f"---------- 资产{code}，目标权重小于等于0，无法下单")
            else:
                # 按当前收盘价计算需要下单的手数
                _size = target_weight * port_value / _price
                self.buy(code, next_row, limit_type, size=_size)

    def notify_order(self, code, date, direction):
        """
            通知订单执行结果
        """
        if self.order_status == 0:
            print(f"---------- {date}: 订单{code}，{direction}，未执行")
        elif self.order_status == 2:
            print(f"---------- {date}: 订单{code}，{direction}，下单失败超过成交量")
        elif self.order_status == 3:
            print(f"---------- {date}: 订单{code}，{direction}，下单失败无足够现金，当前账户现金为{self.broker.cash: .2f}, "
                  f"组合现金为{self.port.cash: .2f}, 组合总价值为{self.port.port_value: .2f}")
        elif self.order_status == 4:
            print(f"---------- {date}: 订单{code}，{direction}，下单失败已触达停板规则")
